package com.xiaozhu.repository.Impl;

import com.alibaba.fastjson.TypeReference;
import com.xiaozhu.common.aiconstant.ChatConstants;
import com.xiaozhu.common.aiconstant.LockConstants;
import com.xiaozhu.enumreation.RedisKeyEnum;
import com.xiaozhu.pojo.dto.ChatDto;

import com.xiaozhu.pojo.entity.ChatSession;
import com.xiaozhu.pojo.entity.ChatType;
import com.xiaozhu.repository.ChatHistoryRepository;
import com.xiaozhu.repository.ChatSessionHistoryRepository;
import com.xiaozhu.repository.ChatTypeHistoryRepository;
import com.xiaozhu.utils.RedisLockTemplate;
import com.xiaozhu.utils.RedisUtils;
import com.xiaozhu.utils.TypeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * InMemoryChatHistoryRepository - 聊天会话管理仓库实现
 * 
 * =====================================================
 *  核心功能
 * =====================================================
 * 管理聊天类型（ChatType）与会话ID列表（ChatSession）的映射关系
 * 
 *  注意：本类只管理会话元数据（type、sessionId、title等），
 *         不存储具体的聊天内容（用户消息、AI回复等）
 *         聊天内容由 LogAspect.java 通过 AOP 自动保存到 ChatContent 表
 * 
 * 实现三级缓存架构：
 * 1. 内存缓存（L1）：ConcurrentHashMap，访问速度 ~1ms
 * 2. Redis 缓存（L2）：分布式缓存，访问速度 ~10ms
 * 3. 数据库（L3）：MySQL 持久化，访问速度 ~200ms
 * 
 * =====================================================
 *  Redis 存储结构（仅会话元数据，不含聊天内容）
 * =====================================================
 * 1. Type → ChatId 列表映射
 *    Key:   "chat:constant:{type}"
 *    Value: List<Integer> - 该类型下所有会话的ID列表
 *    示例:  "chat:constant:chat" → [1366160973, 1366160975]
 *    用途:  前端刷新时快速获取某个聊天类型的所有会话ID
 * 
 * 2. ChatId → 会话元数据映射
 *    Key:   "chat:session:{chatId}"
 *    Value: ChatSession 对象（仅包含会话元数据，不含聊天内容）
 *    示例:  "chat:session:1366160973" → {
 *              id: 123,
 *              chatTypeId: 5,
 *              conversationId: 1366160973,
 *              title: "关于Spring的问题",
 *              userId: 1,
 *              createdAt: "2025-11-04T10:30:00",
 *              updatedAt: "2025-11-04T10:30:00"
 *           }
 *    用途:  快速获取会话基本信息（标题、创建时间等）
 * 
 *  聊天内容存储位置：
 *    - 由 LogAspect.java（AOP切面）自动拦截保存
 *    - 存储到 chat_content 表（包含 user 消息和 assistant 回复）
 *    - 同时保存到 ChatMemory（Spring AI 内存对象）
 * 
 * =====================================================
 *  并发安全机制
 * =====================================================
 * 1. 使用 ConcurrentHashMap 替代 HashMap
 *    - 防止并发读写导致的数据丢失
 *    - 避免 HashMap 扩容时的死循环问题
 * 
 * 2. save() 方法使用 synchronized 同步
 *    - 保护内存缓存、Redis、数据库的写入一致性
 *    - 防止同一 chatId 被重复保存
 *    - 注意：高并发场景下会成为性能瓶颈，可优化为 ReadWriteLock 或分布式锁
 * 
 * 3. 防止缓存穿透
 *    - 空结果缓存 5 分钟
 *    - 避免频繁查询不存在的 type 打垮数据库
 * 
 * =====================================================
 *  性能特性
 * =====================================================
 * - 数据量级：每个 type 对应的 chatId 列表通常 < 1000 个
 * - 缓存命中率（预估）：内存 70%、Redis 20%、DB 10%
 * - 热点数据访问：~1ms（内存命中，用户刷新页面）
 * - 冷数据首次访问：~200ms（DB查询 + 缓存回填）
 * - 降级容错：Redis 失败不影响主流程，自动降级到 DB
 * 
 *  典型调用场景：
 * - 用户刷新页面 → getChtIds() → 返回会话ID列表 → 前端渲染会话列表
 * - 用户发送消息 → save() → 保存/更新会话元数据 → AOP保存聊天内容
 * 
 * =====================================================
 *  注意事项与职责边界
 * =====================================================
 * 1. 职责划分（重要！）
 *     本类负责：管理 ChatType 和 ChatSession（会话元数据）
 *     本类不负责：聊天内容（由 LogAspect.java 的 AOP 切面处理）
 *    
 *    数据流：
 *    用户发送消息 
 *      → ChatController 接收请求
 *      → 本类 save() 保存会话元数据
 *      → AOP LogAspect 拦截并保存聊天内容到 ChatContent 表
 * 
 * 2. 缓存一致性
 *    - save() 方法同时更新三级缓存（内存、Redis、DB）
 *    - 如需删除/更新会话，需要手动清除对应缓存
 * 
 * 3. 内存管理
 *    - 每个 type 最多缓存 ChatConstants.MAX_CACHED_CHAT_IDS_PER_TYPE 个 chatId
 *    - 超过限制自动删除旧数据（FIFO）
 * 
 * 4. Redis Key 命名规范
 *    - 保存和读取的 key 格式必须完全一致
 *    - 当前格式：REDIS_KEY_CONSTANT + ":" + type
 * 
 * 5. 并发性能优化建议
 *    - 高并发场景建议使用 RedisLockTemplate 分布式锁
 *    - 或改用 ReadWriteLock 提升读并发
 * 
 * =====================================================
 * 最近修复
 * =====================================================
 * - [2025-11-04] 修复 Redis key 不一致导致缓存失效的问题
 * - [2025-11-04] 添加 ConcurrentHashMap 防止并发安全问题
 * - [2025-11-04] 添加缓存穿透防护（空结果缓存）
 * 
 * @author 朱慧军
 * @version 2.1
 * @since 2024-xx-xx
 */
@Component
@Slf4j
public class InMemoryChatHistoryRepository implements ChatHistoryRepository {

    /**
     * 内存缓存：type -> chatId 列表
     *  线程安全：使用 ConcurrentHashMap 防止并发冲突
     * 注意：value 使用 ArrayList，操作时需注意并发修改（本类对写操作使用 synchronized 保护）
     */
    private final Map<String, List<Integer>> chatHistory = new ConcurrentHashMap<>();

    @Autowired
    private ChatTypeHistoryRepository chatTypeHistoryRepository;

    @Autowired
    private ChatSessionHistoryRepository chatSessionHistoryRepository;

    //  预留：分布式锁模板（用于未来高并发优化）
    // @Autowired
    // private RedisLockTemplate redisLockTemplate;

    //  删除：未使用的依赖
    // @Autowired
    // private ChatContentRepository chatContentRepository;

    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private RedisLockTemplate redisLockTemplate;

    /**
     * 保存聊天记录
     * @param chatDto 前端传入的聊天信息
     *
     *  并发安全：添加 synchronized 防止并发写入冲突
     * - 保护内存缓存、Redis、数据库的一致性
     * - 防止同一 chatId 被重复保存
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized void save(ChatDto chatDto) {

        log.info("保存数据················");

        // 规范化 type 字符串（例如把 null 或空等统一处理）
        String type = TypeUtils.type(chatDto.getType());
        Integer chatId = chatDto.getChatId();

        // ------------------ 内存缓存 ------------------
        /**
         * computeIfAbsent(key, mappingFunction)：
         * - 如果 key 已存在：直接返回对应的 List<Integer>
         * - 如果 key 不存在：用 mappingFunction 创建一个新 List 并放入 map，返回该 list
         *
         * 这样可以保证 typeCollect 不为 null（无需额外判断 null）
         */
        List<Integer> typeCollect = chatHistory.computeIfAbsent(type, k -> new ArrayList<>());

        // 避免重复添加同一 conversationId
        if (!typeCollect.contains(chatId)) {
            typeCollect.add(chatId);
        }

        // 限制内存缓存列表长度，防止占用过多内存
        //  重构：使用新的常量类名（ChatConstants 中定义）
        int max = ChatConstants.MAX_CACHED_CHAT_IDS_PER_TYPE;
        if (typeCollect.size() > max) {
            // 保留最新的 max 个元素（假设列表尾部为新添加的）
            typeCollect.subList(0, typeCollect.size() - max).clear();
        }

        // ------------------ 数据库持久化 ------------------
        // 1. 查找是否已有该 type（按描述查找）
        ChatType chatType = chatTypeHistoryRepository.findByDescription(type)
                .orElseGet(() -> {
                    // 不存在则创建新的 ChatType 并保存
                    ChatType newType = ChatType.builder()
                            .type(chatDto.getType())  // 保存原始 type 值（非规范化描述）
                            .description(type)        // description 存规范化后的 type 字符串
                            .createdAt(LocalDateTime.now())
                            .updatedAt(LocalDateTime.now())
                            .build();
                    ChatType save = null;
                    try {
                        save = chatTypeHistoryRepository.save(newType);
                        log.info("会话type保存成功：{}", save);
                    } catch (Exception e) {
                        // 保存失败：记录错误并抛出运行时异常以触发事务回滚
                        log.error("会话type保存失败：{}", e.getMessage(), e);
                        throw new RuntimeException("保存ChatType失败", e);
                    }

                    return save;
                });

        // 2. 检查 chatType 是否成功获取或保存
        if (chatType == null || chatType.getId() == null) {
            log.error("ChatType为空或ID为空，无法创建ChatSession");
            throw new RuntimeException("ChatType保存失败，无法创建ChatSession");
        }

        // ------------------ 防止重复 ChatSession 保存 ------------------
        // 先查找是否已经存在同一 conversationId 的 ChatSession，存在则更新 updatedAt 并返回
        // 否则创建新的 ChatSession 并保存
        ChatSession savedSession = chatSessionHistoryRepository
                .findFirstByConversationId(chatDto.getChatId())
                .map(existingSession -> {
                    // 已存在：仅更新时间，避免插入重复记录
                    log.debug("ChatSession 已存在，只更新时间，conversationId: {}", chatDto.getChatId());
                    existingSession.setUpdatedAt(LocalDateTime.now());
                    return chatSessionHistoryRepository.save(existingSession);
                })
                .orElseGet(() -> {
                    // 不存在则创建新的 ChatSession（使用 builder 模式）
                    log.info("创建新的 ChatSession，conversationId: {}", chatDto.getChatId());

                    // 3. 构造会话标题（限制长度，防止字段过长）
                    String title = Optional.ofNullable(chatDto.getPrompt())
                            .map(p -> p.length() > 50 ? p.substring(0, 50) : p)
                            .orElse("新会话");

                    ChatSession chatSession = ChatSession.builder()
                            .chatTypeId(chatType.getId())
                            //  改进：从 BeanContext 获取当前用户ID，若为空则使用默认 1
                            .userId(Optional.ofNullable(com.xiaozhu.utils.BeanContext.getId()).orElse(1))
                            .title(title)
                            .createdAt(LocalDateTime.now())
                            .updatedAt(LocalDateTime.now())
                            .conversationId(chatDto.getChatId())
                            .build();

                    try {
                        ChatSession saved = chatSessionHistoryRepository.save(chatSession);
                        log.info("会话session保存成功：{}", saved);
                        return saved;
                    } catch (Exception e) {
                        // 保存失败：记录并抛出异常，触发事务回滚
                        log.error("会话session保存失败：{}", e.getMessage(), e);
                        throw new RuntimeException("保存ChatSession失败", e);
                    }
                });

        // ------------------ Redis 缓存 ------------------
        try {
            // 构建锁的名称
            String lockKey = LockConstants.LOCKED_KEY + chatDto.getType();
            String lockId = chatDto.getType().toString();

            // 1) type 对应 chatId 列表 的 Redis key
            //    组合规则示例：RedisKeyEnum.REDIS_KEY_CONSTANT.keyTyp() + RoleRedisEnum.ROLE_USERC.getName()
            //    注意：这里请确保 key 的格式与读取侧一致（getChtIds 中的 key 构造方式）
            String redisKeyType = RedisKeyEnum.REDIS_KEY_CONSTANT.keyTyp() + ":" + type;
            // 2) chatId 对应会话实体 的 Redis key，例如：REDIS_KEY_CHAT_ID:key:123
            String redisKeyChatId = RedisKeyEnum.REDIS_KEY_CHAT_ID.keyTyp() + ":" + chatId;

            // 将 typeCollect（List<Integer>）序列化并保存到 Redis
            redisLockTemplate.executeWithLock(lockKey, lockId, ()->{
                redisUtils.set(redisKeyType, typeCollect);
                // 将 savedSession（ChatSession 对象）保存到 Redis，方便快速回取
                redisUtils.set(redisKeyChatId, savedSession);
                return null;
            });
            log.info("Redis保存type列表成功，key: {}", redisKeyType);


            log.info("Redis保存会话实体成功，key: {}", redisKeyChatId);

        } catch (Exception e) {
            // Redis 保存失败不应影响主流程（数据库持久化已完成）
            // 因此只记录日志，不抛出异常（实现降级容错）
            log.error("Redis保存失败：{}", e.getMessage(), e);
        }
    }

    /**
     * 根据 type 获取对应的 chatId 列表
     * @param chatDto 前端传入的 type
     * @return List<Integer> chatId 列表（可能为空列表）
     */
    @Override
    public List<Integer> getChtIds(ChatDto chatDto) {

        // 规范化 type 字符串（与 save 中一致）
        String type = TypeUtils.type(chatDto.getType());

        // Redis key 的构造（注意与 save 中的 key 保持一致）
        String redisKeyType = RedisKeyEnum.REDIS_KEY_CONSTANT.keyTyp() + ":" + type;

        // ------------------ 1. 内存缓存 ------------------
        List<Integer> memoryIds = chatHistory.get(type);
        if (memoryIds != null && !memoryIds.isEmpty()) {
            //  优化：使用 debug 级别记录缓存命中，生产环境避免 INFO 级别太多日志
            log.debug("内存缓存命中，type: {}, chatIds: {}", type, memoryIds);
            return memoryIds;
        }
        log.debug("内存缓存未命中，type: {}", type);

        // ------------------ 2. Redis 缓存 ------------------
        // 通过 RedisUtils 获取并反序列化为 List<Integer>
        List<Integer> redisIds = redisUtils.get(redisKeyType, new TypeReference<List<Integer>>() {});
        if (redisIds != null && !redisIds.isEmpty()) {
            // 回填内存缓存以提高下一次访问速度（热数据回填）
            chatHistory.put(type, redisIds);
            log.debug("Redis缓存命中，type: {}, chatIds: {}", type, redisIds);
            return redisIds;
        }
        log.debug("Redis缓存未命中，尝试从数据库查询，type: {}", type);

        // ------------------ 3. 数据库查询 ------------------
        // 根据原始 type 字段查找 ChatType
        ChatType chatType = chatTypeHistoryRepository.findFirstByType(chatDto.getType());
        if (chatType == null){
            //  防止缓存穿透：缓存空结果（防止频繁落到 DB）
            List<Integer> emptyList = Collections.emptyList();
            chatHistory.put(type, emptyList);
            redisUtils.set(redisKeyType, emptyList, 5L, TimeUnit.MINUTES); // 缓存空结果 5 分钟
            log.warn("ChatType 不存在，已缓存空结果，type={}", chatDto.getType());
            return emptyList;
        }

        // 查询该 chatTypeId 对应的所有 ChatSession
        ChatSession chatSession = ChatSession.builder().chatTypeId(chatType.getId()).build();

        List<ChatSession> chatTypeList = chatSessionHistoryRepository.findAllByChatTypeId(chatSession.getChatTypeId());
        if (chatTypeList == null || chatTypeList.isEmpty()) {
            // 🔧 防止缓存穿透：缓存空结果
            List<Integer> emptyList = Collections.emptyList();
            chatHistory.put(type, emptyList);
            redisUtils.set(redisKeyType, emptyList, 5L, TimeUnit.MINUTES); // 缓存5分钟
            log.info("数据库无数据，已缓存空结果，type={}", type);
            return emptyList;
        }

        // 从 ChatSession 列表中提取 conversationId 列表
        List<Integer> chatId = chatTypeList.stream().map(ChatSession::getConversationId).collect(Collectors.toList());

        // 同步回 Redis 与内存缓存（保持一致）
        chatHistory.put(type, chatId);
        redisUtils.set(redisKeyType, chatId);
        log.info("数据库查询成功：{}", chatId);

        return chatId;
    }

}
